package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

import java.io.EOFException;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.IOException;

/**
 * Encapsulates the state of a user process that is not contained in its
 * user thread (or threads). This includes its address translation state, a
 * file table, and information about the program being executed.
 *
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 *
 * @see	nachos.vm.VMProcess
 * @see	nachos.network.NetProcess
 */
public class UserProcess {
	/**
	 * Allocate a new process.
	 */
	public UserProcess() {
		/* Initialize the file descriptor table */
		fileDescriptorTable = new OpenFile[maxFiles];
		fileDescriptorTable[stdout] = UserKernel.console.openForReading();
		fileDescriptorTable[stdin] = UserKernel.console.openForWriting();

		myChildren = new ArrayList<UserProcess>();

		boolean intStatus = Machine.interrupt().disable();
		pid = nextPid;
		nextPid++;
		Machine.interrupt().restore(intStatus);
		exitStatus = 0;
		exitedProperly = false;
	}

	/**
	 * Allocate and return a new process of the correct class. The class name
	 * is specified by the <tt>nachos.conf</tt> key
	 * <tt>Kernel.processClassName</tt>.
	 *
	 * @return	a new process of the correct class.
	 */
	public static UserProcess newUserProcess() {
		return (UserProcess)Lib.constructObject(Machine.getProcessClassName());
	}

	/**
	 * Execute the specified program with the specified arguments. Attempts to
	 * load the program, and then forks a thread to run it.
	 *
	 * @param	name	the name of the file containing the executable.
	 * @param	args	the arguments to pass to the executable.
	 * @return	<tt>true</tt> if the program was successfully executed.
	 */
	public boolean execute(String name, String[] args) {
		for (String s : args) 
			Lib.debug(dbgProcess, "\targ: "+s);

		if (!load(name, args)) {
			Lib.debug(dbgProcess, "Having problems loading "+name);
			return false;
		}

		processThread = new UThread(this).setName(name);
                if (firstProcess == null) {
                    firstProcess = processThread;
                }
		processThread.fork();

		return true;
	}

	/**
	 * Save the state of this process in preparation for a context switch.
	 * Called by <tt>UThread.saveState()</tt>.
	 */
	public void saveState() {
	}

	/**
	 * Restore the state of this process after a context switch. Called by
	 * <tt>UThread.restoreState()</tt>.
	 */
	public void restoreState() {
		Machine.processor().setPageTable(pageTable);
	}

	/**
	 * Read a null-terminated string from this process's virtual memory. Read
	 * at most <tt>maxLength + 1</tt> bytes from the specified address, search
	 * for the null terminator, and convert it to a <tt>java.lang.String</tt>,
	 * without including the null terminator. If no null terminator is found,
	 * returns <tt>null</tt>.
	 *
	 * @param	vaddr	the starting virtual address of the null-terminated
	 *			string.
	 * @param	maxLength	the maximum number of characters in the string,
	 *				not including the null terminator.
	 * @return	the string read, or <tt>null</tt> if no null terminator was
	 *		found.
	 */
	public String readVirtualMemoryString(int vaddr, int maxLength) {
		Lib.assertTrue(maxLength >= 0);

		byte[] bytes = new byte[maxLength+1];

		int bytesRead = readVirtualMemory(vaddr, bytes);

		for (int length=0; length<bytesRead; length++) {
			if (bytes[length] == 0)
				return new String(bytes, 0, length);
		}

		return null;
	}

	/**
	 * Transfer data from this process's virtual memory to all of the specified
	 * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
	 *
	 * @param	vaddr	the first byte of virtual memory to read.
	 * @param	data	the array where the data will be stored.
	 * @return	the number of bytes successfully transferred.
	 */
	public int readVirtualMemory(int vaddr, byte[] data) {
		return readVirtualMemory(vaddr, data, 0, data.length);
	}

	/**
	 * Transfer data from this process's virtual memory to the specified array.
	 * This method handles address translation details. This method must
	 * <i>not</i> destroy the current process if an error occurs, but instead
	 * should return the number of bytes successfully copied (or zero if no
	 * data could be copied).
	 *
	 * @param	vaddr	the first byte of virtual memory to read.
	 * @param	data	the array where the data will be stored.
	 * @param	offset	the first byte to write in the array.
	 * @param	length	the number of bytes to transfer from virtual memory to
	 *			the array.
	 * @return	the number of bytes successfully transferred.
	 */
	public int readVirtualMemory(int vaddr, byte[] data, int offset,
			int length) {
		if (length == 0)
			return 0;

		Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

		byte[] memory = Machine.processor().getMemory();

		int vpn = Processor.pageFromAddress(vaddr);
		// Invalid memory access
		if (pageTable[vpn] == null) 
			return 0;
		int paddr = vaddrToPaddr(vaddr);
		Lib.assertTrue(paddr >= 0);

		int amount = Math.min(length, pageSize);
		System.arraycopy(memory, paddr, data, offset, amount);

		amount += readVirtualMemory(vaddr+amount, data, amount+offset, length-amount);

		return amount;
	}

	/**
	 * Transfer all data from the specified array to this process's virtual
	 * memory.
	 * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
	 *
	 * @param	vaddr	the first byte of virtual memory to write.
	 * @param	data	the array containing the data to transfer.
	 * @return	the number of bytes successfully transferred.
	 */
	public int writeVirtualMemory(int vaddr, byte[] data) {
		return writeVirtualMemory(vaddr, data, 0, data.length);
	}

	/**
	 * Transfer data from the specified array to this process's virtual memory.
	 * This method handles address translation details. This method must
	 * <i>not</i> destroy the current process if an error occurs, but instead
	 * should return the number of bytes successfully copied (or zero if no
	 * data could be copied).
	 *
	 * @param	vaddr	the first byte of virtual memory to write.
	 * @param	data	the array containing the data to transfer.
	 * @param	offset	the first byte to transfer from the array.
	 * @param	length	the number of bytes to transfer from the array to
	 *			virtual memory.
	 * @return	the number of bytes successfully transferred.
	 */
	public int writeVirtualMemory(int vaddr, byte[] data, int offset,
			int length) {
		if (length == 0)
			return 0;

		Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

		byte[] memory = Machine.processor().getMemory();

		int vpn = Processor.pageFromAddress(vaddr);
		if (pageTable[vpn] == null || pageTable[vpn].readOnly)
			return 0;

		int paddr = vaddrToPaddr(vaddr);
		Lib.assertTrue(paddr >= 0);


		int amount = Math.min(length, pageSize);
		System.arraycopy(data, offset, memory, paddr, amount);

		amount += writeVirtualMemory(vaddr+amount, data, amount+offset, length-amount);

		return amount;
	}

	/**
	 * Load the executable with the specified name into this process, and
	 * prepare to pass it the specified arguments. Opens the executable, reads
	 * its header information, and copies sections and arguments into this
	 * process's virtual memory.
	 *
	 * @param	name	the name of the file containing the executable.
	 * @param	args	the arguments to pass to the executable.
	 * @return	<tt>true</tt> if the executable was successfully loaded.
	 */
	private boolean load(String name, String[] args) {
		Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");

		OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
		if (executable == null) {
			Lib.debug(dbgProcess, "\topen failed");
			return false;
		}

		try {
			coff = new Coff(executable);
		}
		catch (EOFException e) {
			executable.close();
			Lib.debug(dbgProcess, "\tcoff load failed");
			return false;
		}

		// make sure the sections are contiguous and start at page 0
		numPages = 0;
		for (int s=0; s<coff.getNumSections(); s++) {
			CoffSection section = coff.getSection(s);
			if (section.getFirstVPN() != numPages) {
				coff.close();
				Lib.debug(dbgProcess, "\tfragmented executable");
				return false;
			}
			numPages += section.getLength();
		}

		// make sure the argv array will fit in one page
		byte[][] argv = new byte[args.length][];
		int argsSize = 0;
		for (int i=0; i<args.length; i++) {
			argv[i] = args[i].getBytes();
			// 4 bytes for argv[] pointer; then string plus one for null byte
			argsSize += 4 + argv[i].length + 1;
		}
		if (argsSize > pageSize) {
			coff.close();
			Lib.debug(dbgProcess, "\targuments too long");
			return false;
		}

		// program counter initially points at the program entry point
		initialPC = coff.getEntryPoint();	

		// next comes the stack; stack pointer initially points to top of it
		numPages += stackPages;
		initialSP = numPages*pageSize;

		// and finally reserve 1 page for arguments
		numPages++;

		// Now that we know how many pages we need, allocate a page table
		pageTable = new TranslationEntry[numPages];

		if (!loadStack())
			return false;

		if (!loadSections())
			return false;

		if (!loadArguments(argv, args))
			return false;


		return true;
	}

	private boolean loadStack() {
		// Create TranslationEntries for the stack
		for (int i = 0; i < stackPages; i++) {
			int stackVPN = Processor.pageFromAddress(initialSP)-i-1;
			int ppn = UserKernel.getPage();
			if (ppn < 0) {
				return false;
			}
			Lib.debug(dbgProcess, "\nStack VPN: " + stackVPN + ", PPN: " + ppn);
			pageTable[stackVPN] = new TranslationEntry(
					stackVPN, ppn,
					true, false, false, false);
			Lib.debug(dbgProcess, "\nGot page #" + ppn + " - Stack");
			Lib.debug(dbgProcess, "\nThis translation entry: " + pageTable[stackVPN]);
		}

		return true;
	}

	private boolean loadArguments(byte[][] argv, String[] args) {
		// Create TranslationEntry for arguments
		int argsVPN = Processor.pageFromAddress((numPages-1)*pageSize);
		int ppn = UserKernel.getPage();
		if (ppn < 0) {
			return false;
		}
		Lib.debug(dbgProcess, "\nArgs VPN: " + argsVPN + ", PPN: " + ppn);
		pageTable[argsVPN] = new TranslationEntry(
				argsVPN, ppn,
				true, false, false, false);
		Lib.debug(dbgProcess, "\nGot page #" + ppn + " - Arguments");

		// store arguments in last page
		int entryOffset = (numPages-1)*pageSize;
		int stringOffset = entryOffset + args.length*4;

		this.argc = args.length;
		this.argv = entryOffset;

		for (int i=0; i<argv.length; i++) {
			byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
			Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4);
			entryOffset += 4;
			Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) ==
				argv[i].length);
			stringOffset += argv[i].length;
			Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1);
			stringOffset += 1;
		}
		return true;
	}

	/**
	 * Allocates memory for this process, and loads the COFF sections into
	 * memory. If this returns successfully, the process will definitely be
	 * run (this is the last step in process initialization that can fail).
	 *
	 * @return	<tt>true</tt> if the sections were successfully loaded.
	 */
	protected boolean loadSections() {
		if (numPages > Machine.processor().getNumPhysPages()) {
			coff.close();
			Lib.debug(dbgProcess, "\tinsufficient physical memory");
			return false;
		}

		// load sections
		for (int s=0; s<coff.getNumSections(); s++) {
			CoffSection section = coff.getSection(s);

			Lib.debug(dbgProcess, "\tinitializing " + section.getName()
					+ " section (" + section.getLength() + " pages)");

			for (int i=0; i<section.getLength(); i++) {
				int vpn = section.getFirstVPN()+i;
				int ppn = UserKernel.getPage();
				if (ppn < 0) {
					return false;
				}

				Lib.assertTrue(ppn>=0);
				Lib.assertTrue(ppn<Machine.processor().getNumPhysPages());
				section.loadPage(i, ppn);
				Lib.debug(dbgProcess, "Section VPN: " + vpn + ", PPN: " + ppn);
				pageTable[vpn] = new TranslationEntry(
						vpn, ppn,
						true, section.isReadOnly(), false, false);
				Lib.debug(dbgProcess, "Got page #" + ppn + " - Section " + section.getName());

			}
		}
		Lib.debug(dbgProcess, "\tDone loading sections");

		return true;
	}

	/**
	 * Release any resources allocated by <tt>loadSections()</tt>.
	 */
	protected void unloadSections() {

		for (int i = 0; i < fileDescriptorTable.length; i++) {
			OpenFile f = fileDescriptorTable[i];
			if (f != null)
				f.close();
			f = null;
		}

		Lib.debug(dbgProcess, "\n\t\tPage table is " + pageTable.length + " elements.");

		for (TranslationEntry t : pageTable) {
			if (t != null) {
				UserKernel.freePage(t.ppn);
				Lib.debug(dbgProcess, "\nFreed page VPN # " + t.vpn + ", PPN # " + t.ppn);
			} else {
				Lib.debug(dbgProcess, "\nNULL PAGE TABLE ENTRY");
			}
		}

	}    

	/**
	 * Initialize the processor's registers in preparation for running the
	 * program loaded into this process. Set the PC register to point at the
	 * start function, set the stack pointer register to point at the top of
	 * the stack, set the A0 and A1 registers to argc and argv, respectively,
	 * and initialize all other registers to 0.
	 */
	public void initRegisters() {
		Processor processor = Machine.processor();

		// by default, everything's 0
		for (int i=0; i<processor.numUserRegisters; i++)
			processor.writeRegister(i, 0);

		// initialize PC and SP according
		processor.writeRegister(Processor.regPC, initialPC);
		processor.writeRegister(Processor.regSP, initialSP);

		// initialize the first two argument registers to argc and argv
		processor.writeRegister(Processor.regA0, argc);
		processor.writeRegister(Processor.regA1, argv);
	}

	/**
	 * Handle the halt() system call. 
	 */
	private int handleHalt() {
		if (pid == 1) {
			Machine.halt();
			Lib.assertNotReached("Machine.halt() did not halt machine!");
		}
		return 0;
	}

	/* Read size bytes of memory from file to vaddr */
	private int handleRead(int fd, int vaddr, int size) {
		if (fd < 0 || fd >= maxFiles) {
			Lib.debug(dbgProcess, "Invalid file descriptor index");
			return -1;
		}
		if (size < 0) {
			Lib.debug(dbgProcess, "Invalid size.");
			return -1;
		}
		/*
    	if (isReadOnly(vaddr)) {
    		Lib.debug(dbgProcess, "Buffer is read-only");
    		return -1;
    	}
		 */
		OpenFile f = fileDescriptorTable[fd];
		if (f == null) {
			Lib.debug(dbgProcess, "EBADF error: Invalid file descriptor: " + fd);
			return -1;
		}
		byte[] buf = new byte[size];
		int numBytesRead = f.read(buf, 0, size);
		if (numBytesRead == -1) return -1;
		int numBytesWritten = writeVirtualMemory(vaddr, buf, 0, numBytesRead);

		return numBytesWritten;
	}

	/* Write size bytes of memory from vaddr into file */
	private int handleWrite(int fd, int vaddr, int size) {
		if (fd < 0 || fd >= maxFiles) {
			Lib.debug(dbgProcess, "Invalid file descriptor index");
			return -1;
		}
		if (size < 0) {
			Lib.debug(dbgProcess, "Invalid size.");
			return -1;
		}
		OpenFile f = fileDescriptorTable[fd];
		if (f == null) {
			Lib.debug(dbgProcess, "EBADF error: Invalid file descriptor: " + fd);
			return -1;
		}
		byte[] buf = new byte[size];
		int numBytesRead = readVirtualMemory(vaddr, buf);
		int numBytesWritten = f.write(buf, 0, numBytesRead);
		if (numBytesWritten != size) return -1;

		return numBytesWritten;
	}

	private int handleOpenCreate(int vaddr, boolean create) {
		String name = readVirtualMemoryString (vaddr, maxStringSize);
		int fd = -1;
		if (name == null) {
			Lib.debug(dbgProcess, "Could not get filename from vaddr " + vaddr);
			return -1;
		}
		OpenFile file = ThreadedKernel.fileSystem.open(name, create);
		if (file == null) {
			Lib.debug(dbgProcess, "Could not open " + name); 
			return -1;
		}

		/* Find an available file descriptor */
		boolean fileDescriptorTableFull = true;;
		for (int i = 0; i < fileDescriptorTable.length; i++) {
			if (fileDescriptorTable[i] == null) {
				fileDescriptorTable[i] = file;
				fileDescriptorTableFull = false;
				fd = i;
				break;
			}
		}
		if (fileDescriptorTableFull) {
			Lib.debug(dbgProcess, "No free file descriptors.");
			return -1;
		}
		Lib.debug(dbgProcess, "Using FD " + fd);
		return fd;
	}

	private int handleClose(int fd) {
		if (fd < 0 || fd > maxFiles) return -1;

		OpenFile f = fileDescriptorTable[fd];
		if (f == null) {
			Lib.debug(dbgProcess, "No open file for file descriptor " +fd);
			return -1;
		}
		f.close();
		fileDescriptorTable[fd] = null;
		return 0;
	}

	private int handleUnlink(int vaddr) {
		String name = readVirtualMemoryString (vaddr, maxStringSize);
		if (name == null) {
			Lib.debug(dbgProcess, "Could not get filename from vaddr " + vaddr);
			return -1;
		}
		// Return 0 on success, -1 otherwise
		return ThreadedKernel.fileSystem.remove(name) ? 0 : -1;
	}

	private int handleJoin(int processID, int status_vaddr) {
		UserProcess child = null;
		for (UserProcess c : myChildren) {
			if (c.pid == processID)
				child = c;
		}
		if (child == null) 
			return -1;

		Lib.debug(dbgProcess, "Calling join");
		child.processThread().join();
		Lib.debug(dbgProcess, "Finished join");

		int status = child.exitStatus();
		byte[] statusAry = new byte[4];
		Lib.bytesFromInt(statusAry, 0, status);
		int numBytesWritten = writeVirtualMemory(status_vaddr, statusAry);

		if (child.exitedProperly() && numBytesWritten == 4) {
			return 1;
		} else {
			return 0;
		}
	}

	/* Stubs */
	private int handleExit(int status) {

		while (myChildren.size() > 0) {
			myChildren.get(0).myParent = null;
			myChildren.remove(0);
		}

		exitStatus = status;
		exitedProperly = true;
        
        //System.out.println("Return status: " + exitStatus);
        unloadSections();

		Lib.debug(dbgProcess, "\nNumber of free pages: " + UserKernel.getNumFreePages());

		Lib.debug(dbgProcess, "Process thread finishing");
		if (processThread == firstProcess) {
			Kernel.kernel.terminate();
		} else {
			processThread.finish();
		}
		Lib.debug(dbgProcess, "Process thread finished");

		return 0;
	}

	private int handleExec(int name_vaddr, int argc, int argv_vaddr) {
		if (argc < 0)  {
			Lib.debug(dbgProcess, "Not enough arguments");
			return -1;    // Number of arguments must be *non-negative*
		}
		UserProcess child = UserProcess.newUserProcess();
		String childName = 
			readVirtualMemoryString(name_vaddr, maxStringSize);
		String[] args = new String[argc];

		for (int i = 0; i < argc; i++) {
			byte[] argPointerBytes = new byte[4];
			readVirtualMemory(argv_vaddr+(i*4), argPointerBytes);
			int argPointer = Lib.bytesToInt(argPointerBytes, 0);
			String s = readVirtualMemoryString(argPointer,
					maxStringSize);
			args[i] =  s;
		}
		if (childName == null) {
			// Read memory incorrectly
			Lib.debug(dbgProcess, "Invalid name");
			return -1;
		}
		myChildren.add(child);
		child.myParent = this;
        
        //System.out.println("CHILD: " + childName);

		return child.execute(childName, args) ? child.pid : -1;
	}

	private static final int
	syscallHalt = 0,
	syscallExit = 1,
	syscallExec = 2,
	syscallJoin = 3,
	syscallCreate = 4,
	syscallOpen = 5,
	syscallRead = 6,
	syscallWrite = 7,
	syscallClose = 8,
	syscallUnlink = 9;

	/**
	 * Handle a syscall exception. Called by <tt>handleException()</tt>. The
	 * <i>syscall</i> argument identifies which syscall the user executed:
	 *
	 * <table>
	 * <tr><td>syscall#</td><td>syscall prototype</td></tr>
	 * <tr><td>0</td><td><tt>void halt();</tt></td></tr>
	 * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr>
	 * <tr><td>2</td><td><tt>int  exec(char *name, int argc, char **argv);
	 * 								</tt></td></tr>
	 * <tr><td>3</td><td><tt>int  join(int pid, int *status);</tt></td></tr>
	 * <tr><td>4</td><td><tt>int  creat(char *name);</tt></td></tr>
	 * <tr><td>5</td><td><tt>int  open(char *name);</tt></td></tr>
	 * <tr><td>6</td><td><tt>int  read(int fd, char *buffer, int size);
	 *								</tt></td></tr>
	 * <tr><td>7</td><td><tt>int  write(int fd, char *buffer, int size);
	 *								</tt></td></tr>
	 * <tr><td>8</td><td><tt>int  close(int fd);</tt></td></tr>
	 * <tr><td>9</td><td><tt>int  unlink(char *name);</tt></td></tr>
	 * </table>
	 * 
	 * @param	syscall	the syscall number.
	 * @param	a0	the first syscall argument.
	 * @param	a1	the second syscall argument.
	 * @param	a2	the third syscall argument.
	 * @param	a3	the fourth syscall argument.
	 * @return	the value to be returned to the user.
	 */
	public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
		switch (syscall) {
		case syscallHalt:   return handleHalt();
		case syscallExit:   return handleExit(a0);
		case syscallExec:    return handleExec(a0, a1, a2);
		case syscallJoin:    return handleJoin(a0, a1);
		case syscallCreate:  return handleOpenCreate(a0, true);
		case syscallOpen:    return handleOpenCreate(a0, false);
		case syscallRead:   return handleRead(a0, a1, a2);
		case syscallWrite:  return handleWrite(a0, a1, a2);
		case syscallClose:  return handleClose(a0);
		case syscallUnlink: return handleUnlink(a0);


		default:
			Lib.debug(dbgProcess, "Unknown syscall " + syscall);
		Lib.assertNotReached("Unknown system call!");
		}
		return 0;
	}

	/**
	 * Handle a user exception. Called by
	 * <tt>UserKernel.exceptionHandler()</tt>. The
	 * <i>cause</i> argument identifies which exception occurred; see the
	 * <tt>Processor.exceptionZZZ</tt> constants.
	 *
	 * @param	cause	the user exception that occurred.
	 */
	public void handleException(int cause) {
		Processor processor = Machine.processor();

		switch (cause) {
		case Processor.exceptionSyscall:
			int result = handleSyscall(processor.readRegister(Processor.regV0),
					processor.readRegister(Processor.regA0),
					processor.readRegister(Processor.regA1),
					processor.readRegister(Processor.regA2),
					processor.readRegister(Processor.regA3)
			);
			processor.writeRegister(Processor.regV0, result);
			processor.advancePC();
			break;				       

		default:
                    Lib.debug(dbgProcess, "Unexpected exception: " + cause + " " +
                            Processor.exceptionNames[cause]);
                    Lib.assertNotReached("Unexpected exception");
                    break;
		}
	}

	/** Returns true iff process is allowed access at vaddr */
	private boolean isReadOnly(int vaddr) {
		int vpn = vaddr / pageSize;
		for (int i = 0; i < pageTable.length; i++) {
			if (pageTable[i].vpn == vpn)
				return pageTable[i].readOnly;
		}
		return false;
	}

	/** Converts vaddrs to paddrs 
	 * @param    vaddr the virtual address to convert
	 * @return   -1 if the vaddr is not in the page table, otherwise the
	 *           address in physical memory
	 */
	protected int vaddrToPaddr(int vaddr) {
		int vpn = Processor.pageFromAddress(vaddr);
		int offset = Processor.offsetFromAddress(vaddr);

		int ppn = pageTable[vpn] == null ? -1 : pageTable[vpn].ppn;
		// If vaddr not in page table, return -1
		if (ppn == -1) 
			return -1;

		return (ppn*pageSize)+offset;
	}

	public KThread processThread() {
		return processThread;
	}

	public int exitStatus() {
		return exitStatus;
	}

	public boolean exitedProperly() {
		return exitedProperly;
	}

	/** The program being run by this process. */
	protected Coff coff;

	/** This process's page table. */
	protected TranslationEntry[] pageTable;

	/** The number of contiguous pages occupied by the program. */
	protected int numPages;

	/** The number of pages in the program's stack. */
	protected final int stackPages = 8;

	/** The maximum number of open files for this process */
	protected final int maxFiles = 16;

	/** The default file descriptors for stdout and stdin */
	protected final int stdout = 0;
	protected final int stdin = 1;

	/** Maximum length of a string */
	protected final int maxStringSize = 256;

	/** The file descriptor list for this process */
	protected OpenFile[] fileDescriptorTable;

	protected int initialPC, initialSP;
	protected int argc, argv;

	private static final int pageSize = Processor.pageSize;
	private static final char dbgProcess = 'a';

	private UserProcess myParent;    // Doesn't want to work when protected. Weird.
	private ArrayList<UserProcess> myChildren;
	protected int pid;
	protected static int nextPid = 1; // start at 1?
	private int exitStatus;
	private boolean exitedProperly;

	protected KThread processThread;
        protected static KThread firstProcess = null;
}
